#include <stdio.h>

#include <ctype.h>
#include <assert.h>
#include <cstring>
#include <cstdlib>

#ifdef __GNUC__
#include <strings.h>
#define STRTOK strtok_r
#elif defined(_MSC_VER)
#include <string.h>
#define STRTOK strtok_s
#endif


#include "abstract_animation.h"
#include "../core/inspector.h"

using namespace Commands;
using namespace Commands::Core;

AbstractAnimation::AbstractAnimation()
: currentFrameIdx(0)
, loadedFrames(0)
, sequencedFrameIdx(0)
{}
//-----------------------------------------------------------------------------
AbstractAnimation::~AbstractAnimation()
{}
//-----------------------------------------------------------------------------
void AbstractAnimation::Accept(const Inspector& inspector)
{
    const_cast<Inspector&>(inspector).Inspect(*this);
}
//-----------------------------------------------------------------------------
void AbstractAnimation::SetCurrentFrame(const size_t frame)
{
    if(frame >= 0 && frame < GetFramesCount())
    {
        sequencedFrameIdx = frame;
    }
}
//-----------------------------------------------------------------------------
int AbstractAnimation::GetCurrentFrame(void) const
{
    return sequencedFrames[sequencedFrameIdx];
}
//-----------------------------------------------------------------------------
int AbstractAnimation::GetFramesCount(void) const
{
    return sequencedFrames.size();
}
//-----------------------------------------------------------------------------
void AbstractAnimation::NextFrame(void)
{
    if(sequencedFrameIdx >= GetFramesCount())
    {
        sequencedFrameIdx = GetFramesCount() - 1;
    }
    else
    {
        ++sequencedFrameIdx;
    }
}
//-----------------------------------------------------------------------------
void AbstractAnimation::SetSequence(const char* const sequence)
{
    char str[1024];
    strcpy(str, sequence);
    coma_split(str);
}
//-----------------------------------------------------------------------------
void AbstractAnimation::DropSequence(void)
{
    sequencedFrames.clear();
    for(size_t i = 0; i < loadedFrames; ++i)
    {
        sequencedFrames.push_back(i);
    }
}
//-----------------------------------------------------------------------------
void AbstractAnimation::make_range(const size_t first_number, const size_t last_number)
{
    const int step = (last_number > first_number) ? 1 : (last_number < first_number) ? -1 : 0;

    if(step != 0)
    {
        for(size_t i = first_number ; ; i += step)
        {
            sequencedFrames.push_back(i);
            if(i == last_number) break;
        }
    }
    else
    {
        sequencedFrames.push_back(first_number);
        sequencedFrames.push_back(last_number);
    }
}
//-----------------------------------------------------------------------------
void AbstractAnimation::dash_split(char* str)
{
    if(strchr(str,'-') == NULL && is_valid(str))
    {
        sequencedFrames.push_back(make_number(str));
        return;
    }

    char* ptr;
    bool first_defined = false;
    bool range_added = false;

    int first_number, last_number;
    for(char* token = STRTOK(str, "-", &ptr); token; token = STRTOK(NULL, "-", &ptr))
    {
        if(!is_valid(token)) continue;

        if(!first_defined)
        {
            first_defined = true;
            first_number = make_number(token);
        }
        else // if(first_defined)
        {
            last_number = make_number(token);

            if(range_added) sequencedFrames.pop_back();

            make_range(first_number, last_number);

            first_number = last_number;
            range_added = true;
        }
    }
}
//-----------------------------------------------------------------------------
void AbstractAnimation::coma_split(char* str)
{
    sequencedFrames.clear();

    if(strchr(str, ',') == NULL)
    {
        dash_split(str);
        return;
    }

    char* ptr;
    for(char* token = STRTOK(str, ",", &ptr); token; token = STRTOK(NULL, ",", &ptr))
    {
        dash_split(token);
    }
}
//-----------------------------------------------------------------------------
size_t AbstractAnimation::make_number(char* const str) const
{
    if(strlen(str) == 1)
    {
       if(strcmp(str, "(") == 0)
       {
           assert(loadedFrames >= 3);
           return 1;
       }
       if(strcmp(str, ")") == 0)
       {
           assert(loadedFrames >= 3);
           return loadedFrames - 2;
       }
       if(strcmp(str, "[") == 0)
       {
           assert(loadedFrames >= 2);
           return 0;
       }
       if(strcmp(str, "]") == 0)
       {
           assert(loadedFrames >= 2);
           return loadedFrames - 1;
       }
    }
    return atoi(str);
}
//-----------------------------------------------------------------------------
bool AbstractAnimation::is_valid(char* const str) const
{
    if(!str) return false;

    if(strlen(str) == 1)
    {
        if(strcmp(str, "(") == 0 || strcmp(str, ")") == 0 ||
           strcmp(str, "[") == 0 || strcmp(str, "]") == 0 ||
           isdigit(str[0]))
        {
            return true;
        }
    }
    else
    {
        const int number = atoi(str);
        if(number > 0 && number < loadedFrames)
        {
            return true;
        }
    }
    return false;
}
//-----------------------------------------------------------------------------
